#include "ET++.ph"
#ifdef __GNUG__
#pragma implementation
#endif

#include "TextPainter.h"

#include "Class.h"
#include "Text.h"
#include "Font.h"
#include "WindowSystem.h"
#include "TextFormatter.h"   // -> LineDesc
#include "Bitmap.h"
#include "Math.h"
#include "PrintManager.h"
#include "String.h"
#include "TextMarks.h"

//---- output map for control characters ---------------------------------------

static RGBColor *invisColor;
static Font
	*symbolFont= 0,
	*noSymbolFont= 0;

const byte
	o_symbol= 01,  // use symbol font
	o_grey  = 02,  // use grey color
	o_dot   = 04,  // mark character with a dot
	o_nomove= 010;  // draw character without moving cp
		
static struct outmap {
	const char *name;       // 0 == don't map
	byte code;
	byte c;
} OutMap[] = {
	{ "section",            o_grey },                       /* 0x00 */
	{ 0,                    0 },                            /* 0x01 */
	{ 0,                    0 },                            /* 0x02 */
	{ 0,                    0 },                            /* 0x03 */
	{ 0,                    0 },                            /* 0x04 */
	{ 0,                    0 },                            /* 0x05 */
	{ 0,                    0 },                            /* 0x06 */
	{ 0,                    0 },                            /* 0x07 */
	{ 0,                    0 },                            /* 0x08 */
	{ "angleright",         o_symbol|o_grey|o_nomove },     /* 0x09 */
	{ "paragraph",          o_grey },                       /* 0x0a */
	{ 0,                    0 },                            /* 0x0b */
	{ "arrowdbldown",       o_symbol|o_grey },              /* 0x0c */
	{ "carriagereturn",     o_symbol|o_grey },              /* 0x0d */
	{ 0,                    0 },                            /* 0x0e */
	{ 0,                    0 },                            /* 0x0f */
	{ 0,                    0 },                            /* 0x10 */
	{ 0,                    0 },                            /* 0x11 */
	{ 0,                    0 },                            /* 0x12 */
	{ 0,                    0 },                            /* 0x13 */
	{ 0,                    0 },                            /* 0x14 */
	{ 0,                    0 },                            /* 0x15 */
	{ 0,                    0 },                            /* 0x16 */
	{ 0,                    0 },                            /* 0x17 */
	{ 0,                    0 },                            /* 0x18 */
	{ 0,                    0 },                            /* 0x19 */
	{ 0,                    0 },                            /* 0x1a */
	{ 0,                    0 },                            /* 0x1b */
	{ 0,                    0 },                            /* 0x1c */
	{ 0,                    0 },                            /* 0x1d */
	{ 0,                    0 },                            /* 0x1e */
	{ 0,                    0 }                             /* 0x1f */
};

static inline bool UseSymbolFont(byte c)
	{ return c & o_symbol; }

static inline bool UseGrey(byte c)
	{ return gColor && (c & o_grey); }

static inline bool NoMove(byte c)
	{ return c & o_nomove; }

//---- TextPainter -------------------------------------------------------------

NewMetaImpl(TextPainter,Object, (T(nextFontChange), TP(ps), TP(sp), T(graphicChar)));

TextPainter::TextPainter()
{
	linebuf= 0;
}

TextPainter::~TextPainter()
{
	SafeDelete(linebuf);
}

void TextPainter::Init(Text *t, int from, int to)
{
	line= t->GetLineAccess(&linebuf, from, to);
	ps= t->GetParaStyle(from);
	nextFontChange= -1;
	graphicChar= t->GetMarkChar();
}

int TextPainter::Strip(Text *t, int from, int to)
{
    for ( ; to-1 >= from && ((*t)[to-1] == ' ' || (*t)[to-1] == '\t'); to--) 
	;
    return to;
}

int TextPainter::CalcSpace(Text *t, int width, int from, int to,
					int &space, int &blanks, int &ntab)
{
	int nBlanks, ch;

	nBlanks= space= blanks= ntab= 0;

	to= Strip(t, from, to);

	int w= CalcWidth(t, from, to);
	for (int i= from; line && i < to; i++) {
		ch= line[i-from];
		switch(ch) {
		case '\t':
			ntab++;
			nBlanks= 0;
			break;
		case ' ':
			nBlanks++;
			break;
		default:
			break;
		}
	}
	if (nBlanks) {
		blanks= (width - w) % nBlanks;
		space= (width - w) / nBlanks;
	}
	return to;
}

int TextPainter::CalcIndent(Text *t, ParaStyle *p, int at, int &width)
{
	int indent= p->GetProperty(eTxtPLeft);
	if (t->IsParaStart(at))
		indent= Math::Max(0, indent+p->GetProperty(eTxtPFirstIndent));
	width= width-indent-p->GetProperty(eTxtPRight);
	return indent;
}

bool TextPainter::IsJustified(Text *t, int eol)
{
	TxtParaAlign a= (TxtParaAlign)ps->GetProperty(eTxtPAlign);
	byte ch= (*t)[eol-1];
	return a == eTxtParaJustified && eol != t->Size() && !t->IsBreak(ch);
}

void TextPainter::ShowInvisible(byte ch)
{
	Ink *sInk;
	Point sPoint;
	Font *sFont;
	static bool OutMapOk= FALSE;

	if (!OutMapOk) {
		for (int i= 0; i <= 0x1f; i++)
			if (OutMap[i].name)
				if (OutMap[i].code & o_symbol)
					OutMap[i].c= gSymbolEncoding->NameToCode(OutMap[i].name);
				else
					OutMap[i].c= gStdEncoding->NameToCode(OutMap[i].name);
		OutMapOk= TRUE;
	}
	byte code= OutMap[ch].code;

	if (code) {
		if (UseGrey(code)) {
			sInk= GrGetTextInk();
			if (invisColor == 0)
				invisColor= new_Grey(0.5);
			GrSetTextInk(invisColor);
		}
		if (NoMove(code))
			sPoint= GrGetTextPos();
		
		sFont= GrGetFont();
		if (UseSymbolFont(code)) {
			if (symbolFont == 0)
				symbolFont= new_Font(eFontSymbol, 10);
			GrSetFont(symbolFont);
		} else {
			if (noSymbolFont == 0)
				noSymbolFont= new_Font(eFontTimes, 10);
			GrSetFont(noSymbolFont);
		}
		
		GrDrawChar(OutMap[ch].c);
		
		if (UseGrey(code))
			GrSetTextInk(sInk);
		if (NoMove(code))
			GrTextMoveto(sPoint);
		GrSetFont(sFont);
	}
}

void TextPainter::SetCharStyle(CharStyle *cs)
{
	GrSetFont(cs->GetFont());
	GrSetTextInk(cs->GetInk());
}

int TextPainter::FirstCharPos(Text *t, int width, int from, int to)
{
	//---- strip trailing whitespace, blank, tab or new line
	byte ch= (*t)[to-1];
	if (Isspace(ch) || t->IsBreak(ch))
		to= Math::Max(0, --to);

	TxtParaAlign align= (TxtParaAlign)ps->GetProperty(eTxtPAlign);
	switch (align) {
	case eTxtParaCenter:
		return (width - CalcWidth(t, from, to))/2;
	case eTxtParaRight:
		return width - CalcWidth(t, from, to);
	default:
		break;
	}
	return 0;
}

int TextPainter::CalcWidth(Text *t, int from, int to)
{
	register int i, ch, w= 0;
	int d, indent= CalcIndent(t, ps, from, d);

	nextFontChange= t->GetNextFontChange(from, sp);

	for (i= from; line && i < to; i++) {
		TestFontChange(t, i, sp);
		ch= line[i-from];
		if (ch == '\t')
			w+= CalcTabWidth(t, ps, w+indent, i);
		else {
			if (TestVisualMark(t, i, ch))
				w+= t->GetVisualMarkAt(i)->GetExtent().x;
			else
				w+= sp->GetFont()->Width(ch);
		}
	}
	return w;
}

void TextPainter::Draw(Text *t, const Point &pp, int from, int to,
						const Rectangle &lr, const Rectangle &clip, bool invis)
{
	register byte ch;
	int ntab= 0, tabw, longBlanks= 0, addSpace= 0, seenTabs= 0;
	Point p(pp), left(pp);
	int w= lr.extent.x;

	Init(t, from, to);
	bool isJust= IsJustified(t, to);
	int indent= CalcIndent(t, ps, from, w);

	p.x+= FirstCharPos(t, w, from, to) + indent;
	if (invis) {
		if (ps->GetProperty(eTxtPNoBreak))
			ShowNoBreak(p, lr, t, from, to);
		if (ps->GetProperty(eTxtPKeepNext))
			ShowKeepNext(p, lr, t, from, to);
	}

	if (Isspace((*t)[to-1]) && !invis)
		to= Math::Max(from, --to);
		
	if (isJust)
		CalcSpace(t, w, from, to, addSpace, longBlanks, ntab);
	nextFontChange= t->GetNextFontChange(from, sp);
	SetCharStyle(sp);
	GrTextMoveto(p);

	for (int i= from, nBlanks= 0; line && i < to; i++) {
		ch= line[i-from];
		
		if (invis && !gPrinting && Iscntrl(ch))
			ShowInvisible(ch);
				
		if (TestFontChange(t, i, sp))
			SetCharStyle(sp);

		switch (ch) {
		case '\t':
			tabw= CalcTabWidth(t, ps, GrGetTextPos().x-left.x, i);
			GrTextAdvance(tabw);
			seenTabs++;
			break;

		case ' ':
			GrTextAdvance(sp->GetFont()->Width(' '));
			if (seenTabs == ntab) {
				if (addSpace)
					GrTextAdvance(addSpace);
				if (nBlanks < longBlanks)
					GrTextAdvance(1);
				nBlanks++;
			}
			break;
		default:
			if (TestVisualMark(t, i, ch)) {
				DrawVisualMark(GrGetTextPos(),
					t->GetVisualMarkAt(i),
					clip,
					lr,
					invis && !gPrinting
				);
				nextFontChange= t->GetNextFontChange(i, sp);
				SetCharStyle(sp);
			} else if (!Iscntrl(ch))
				GrDrawChar(ch);
		}
	}
}

void TextPainter::DrawVisualMark(const Point &pp, VisualMark *vmp,
						const Rectangle &clip, const Rectangle &lr, bool border)
{
	Point p(pp);
	p.y-= vmp->Base();
	Point tp(GrGetTextPos());
	vmp->Draw(p, clip, lr, border);
	tp.x += vmp->GetExtent().x;
	GrTextMoveto(tp);
}

int TextPainter::Map(Text *t, int from, int to, int stop, int x,
													int width, int *posX)
{
	register byte ch;
	int ntab= 0, longBlanks= 0, addSpace= 0, seenTabs= 0, cx= 0, wx= 0;

	Init(t, from, to);
	int indent= CalcIndent(t, ps, from, width);

	int fx= FirstCharPos(t, width, from, to) + indent;
	bool isJust= IsJustified(t, to);

	// make new lines at end of line not selectable
	ch= (*t)[to-1];
	if (to-from != 0 && (t->IsBreak(ch)))
		to--;

	x-= fx;
		
	if (isJust)
		to= CalcSpace(t, width, from, to, addSpace, longBlanks, ntab);
		
	nextFontChange= t->GetNextFontChange(from, sp);

	for (int i= from, nBlanks= 0; line && i < to && i < stop; i++) {
		TestFontChange(t, i, sp);
		switch (ch= line[i-from]) {
		case '\t':
			wx= CalcTabWidth(t, ps, cx+indent, i);
			seenTabs++;
			break;
		case ' ':
			wx= sp->GetFont()->Width(' ');
			if (seenTabs == ntab) {
				wx += addSpace;
				if (nBlanks < longBlanks)
					wx++;
				nBlanks++;
			}
			break;
		default:
			if (TestVisualMark(t, i,ch))
				wx= t->GetVisualMarkAt(i)->GetExtent().x;
			else
				wx= sp->GetFont()->Width(ch);
			break;
		}
		if (cx + (wx/2) > x)
			break;
		cx+= wx;
	}
	if (posX)
		*posX= cx+fx;
	return i;
}

int TextPainter::GetFormatWidth(Text *t, int at, int width)
{
	CalcIndent(t, t->GetParaStyle(at), at, width);
	return width;
}

int TextPainter::LineWidth(Text *t, int from, int to)
{
	Init(t, from, to);
	if (t->IsBreak((*t)[to-1]))
		to= Math::Max(0, --to);
	return CalcWidth(t, from, to);
}

void TextPainter::LineHeight(Text *t, LineDesc &ld, int start, int end)
{
	ps= t->GetParaStyle(start);
	int sp= ps->GetProperty(eTxtPSpacing);
	int d= sp-ld.lnHeight;
	if (d > 0) {
		ld.lnHeight+= d;
		ld.lnAscent+= d;
	}
	if (t->IsParaStart(start)) {
		int sb= ps->GetProperty(eTxtPSpaceBefore);
		ld.lnHeight+= sb;
		ld.lnAscent+= sb;
	}
	if (t->IsParaEnd(end-1)) {
		int sa= ps->GetProperty(eTxtPSpaceAfter);
		ld.lnHeight+= sa;
	}
}

int TextPainter::CalcTabWidth(Text *t, ParaStyle *p, int x, int at)
{
	const ParaTabs &tabs= ps->GetTabs();
	int indent= p->GetProperty(eTxtPLeft);
	TxtParaAlign align= (TxtParaAlign)p->GetProperty(eTxtPAlign);
	int tx, w= 0;

	// tabs are only interpreted if text is left aligned
	if (align != eTxtParaLeft && align != eTxtParaJustified)
		return 0;
		
	for(int i= 0; i < tabs.ntabs; i++) {
		tx= tabs.stops[i].x;
		if (tx > x) {
			// indent is an implizit tab stop (flush left)
			if (indent > x)
				return indent-x;
			switch(tabs.stops[i].kind) {
			case eTxtTLeft:
				return tx-x;
			case eTxtTRight:
				w= TabColWidth(t, at, "\t");
				return (Math::Max(0, tx-x-w));
			case eTxtTCenter:
				w= TabColWidth(t, at, "\t");
				return (Math::Max(0, tx-x-(w/2)));
			case eTxtTDecimal:
				w= TabColWidth(t, at, "\t.");
				return (Math::Max(0, tx-x-w));
			}
		}
	}
	// default tabs
	int tw= t->GetDefTab();
	if (indent > x)
		return indent-x;
	if (tw > 0)
		return (((x / tw)+1) * tw - x);
	return 0;
}

int TextPainter::TabColWidth(Text *t, int start, char *stops)
{
	register int w= 0, ch;
	int wn;
	AutoTextIter next(t, start+1);

	while (ch= next(&wn)) {
		if (ch == cEOT || strchr(stops, ch) != 0 || Text::IsBreak(ch))
			break;
		w+= wn;
	}
	return w;
}

int TextPainter::Tabulate(Text *t, int x, int at, int lineStart)
{
	int w;
	ps= t->GetParaStyle(at);
	int indent= CalcIndent(t, ps, lineStart, w);
	return CalcTabWidth(t, ps, x+indent, at);
}

void TextPainter::ShowNoBreak(const Point&, const Rectangle&, Text *, int, int)
{
}

void TextPainter::ShowKeepNext(const Point&, const Rectangle&, Text *, int, int)
{
}

